﻿/*
 * DotNetCrypt - an open source library of cryptographic algorithms for .NET
 * Copyright (C) 2009 David Musgrove
 * 
 * This file is part of DotNetCrypt.
 *
 * DotNetCrypt is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * DotNetCrypt is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

using System;

namespace DotNetCrypt
{
// ReSharper disable InconsistentNaming
    /// <summary>
    /// Performs a cryptographic transformation of data using the
    /// <see cref="DESX" /> algorithm. This class cannot be inherited.
    /// </summary>
    public sealed class DESXManagedTransform : ManagedTransformBase
// ReSharper restore InconsistentNaming
    {
        static private readonly byte[] _substitutionTable = {
                                                                189, 86, 234, 242, 162, 241, 172, 42, 176, 147, 209, 156, 27, 51, 253, 208,
                                                                48, 4, 182, 220, 125, 223, 50, 75, 247, 203, 69, 155, 49, 187, 33, 90,
                                                                65, 159, 225, 217, 74, 77, 158, 218, 160, 104, 44, 195,39, 95, 128, 54,
                                                                62, 238, 251, 149, 26, 254, 206, 168, 52, 169, 19, 240,166, 63, 216, 12,
                                                                120, 36, 175, 35, 82, 193, 103, 23, 245, 102, 144, 231,232, 7, 184, 96,
                                                                72, 230, 30, 83, 243, 146, 164, 114, 140, 8, 21, 110,134, 0, 132, 250,
                                                                244, 127, 138, 66, 25, 246, 219, 205, 20, 141, 80, 18,186, 60, 6, 78,
                                                                236, 179, 53, 17, 161, 136, 142, 43, 148, 153, 183, 113,116, 211, 228, 191,
                                                                58, 222, 150, 14, 188, 10, 237, 119, 252, 55, 107, 3,121, 137, 98, 198,
                                                                215, 192, 210, 124, 106, 139, 34, 163, 91, 5, 93, 2, 117, 213, 97, 227,
                                                                24, 143, 85, 81, 173, 31, 11, 94, 133, 229, 194, 87, 99,202, 61, 108,
                                                                180, 197, 204, 112, 178, 145, 89, 13, 71, 32, 200, 79,88, 224, 1, 226,
                                                                22, 56, 196, 111, 59, 15, 101, 70, 190, 126, 45, 123,130, 249, 64, 181,
                                                                29, 115, 248, 235, 38, 199, 135, 151, 37, 84, 177, 40,170, 152, 157, 165,
                                                                100, 109, 122, 212, 16, 129, 68, 239, 73, 214, 174, 46,221, 118, 92, 47,
                                                                167, 28, 201, 9, 105, 154, 131, 207, 41, 57, 185, 233,76, 255, 67, 171
                                                            };

        private readonly DESManagedTransform _desTransform;
        private readonly uint[] _preWhitening;
        private readonly uint[] _postWhitening;

        internal DESXManagedTransform(ISymmetricAlgorithm algorithm, byte[] rgbKey, byte[] rgbIv, TransformDirection transformDirection)
            : base(algorithm, rgbIv, transformDirection, Endianness.Big)
        {
            Mode = algorithm.ExtendedMode;
            var des = new DESManaged();
            des.ExtendedMode = algorithm.ExtendedMode;
            des.Padding = algorithm.Padding;
            des.NonceCombinationMode = algorithm.NonceCombinationMode;
            des.RegisterShiftSize = algorithm.RegisterShiftSize;
            var key = new byte[0x08];
            Array.Copy(rgbKey, 0, key, 0, 0x08);
            _desTransform = new DESManagedTransform(des, key, rgbIv, transformDirection);
            _preWhitening = Utils.BytesToWordsBigEndian(rgbKey, 0x08, 0x08);
            if (rgbKey.Length > 0x10)
            {
                _postWhitening = Utils.BytesToWordsBigEndian(rgbKey, 0x10, 0x08);
            }
            else
            {
                _postWhitening = GetPostWhiteningFromHash(rgbKey);
            }
        }

        static private uint[] GetPostWhiteningFromHash(byte[] key)
        {
            var postWhitening = new byte[8];
            for (int i = 0; i < 16; i++)
            {
                int index = postWhitening[0] ^ postWhitening[1];
                for (int j = 0; j < 7;) postWhitening[j] = postWhitening[++j];
                postWhitening[7] = (byte)(_substitutionTable[index] ^ key[i]);
            }
            return Utils.BytesToWordsBigEndian(postWhitening);
        }

        internal bool IsWeakKey
        {
            get
            {
                return false;
            }
        }

        /// <summary>
        /// Performs the encryption transformation on a block of bytes that
        /// have been translated into words using the big endian convention.
        /// </summary>
        /// <param name="plain">
        /// The words to encrypt.
        /// </param>
        [CLSCompliant(false)]
        protected internal override void Encrypt(uint[] plain)
        {
            plain[0] ^= _preWhitening[0];
            plain[1] ^= _preWhitening[1];
            _desTransform.Encrypt(plain);
            plain[0] ^= _postWhitening[0];
            plain[1] ^= _postWhitening[1];
        }

        /// <summary>
        /// Performs the decryption transformation on a block of bytes that
        /// have been translated into words using the big endian convention.
        /// </summary>
        /// <param name="cipher">
        /// The words to decrypt.
        /// </param>
        [CLSCompliant(false)]
        protected internal override void Decrypt(uint[] cipher)
        {
            cipher[0] ^= _postWhitening[0];
            cipher[1] ^= _postWhitening[1];
            _desTransform.Decrypt(cipher);
            cipher[0] ^= _preWhitening[0];
            cipher[1] ^= _preWhitening[1];
        }

        /// <summary>
        /// Clears all potentially sensitive data stores.
        /// </summary>
        protected internal override void Reset()
        {
            if (_preWhitening != null)
            {
                Array.Clear(_preWhitening, 0, _preWhitening.Length);
            }
            if (_postWhitening != null)
            {
                Array.Clear(_postWhitening, 0, _postWhitening.Length);
            }
            if (_desTransform != null)
            {
                _desTransform.Reset();
            }
            base.Reset();
        }
    }
}